import * as vscode from 'vscode';


class GPDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
    public provideDocumentSymbols(
        document: vscode.TextDocument,
        token: vscode.CancellationToken
    ): Promise<vscode.DocumentSymbol[]> {
        return new Promise((resolve) => {
            const symbols: vscode.DocumentSymbol[] = [];
            
            // Process each line of the document
            for (let i = 0; i < document.lineCount; i++) {
                const line = document.lineAt(i);
                const text = line.text.trim();
                
                // Match method or to definitions
                const methodMatch = text.match(/^(method|to)\s+(\w+)\s+(\w+)?/);
                if (methodMatch) {
                    const type = methodMatch[1]; // 'method' or 'to'
                    const name = methodMatch[2];
                    const className = methodMatch[3];
                    
                    // Find the end of the method/function block
                    let endLine = i;
                    let bracketCount = 1;
                    while (endLine < document.lineCount && bracketCount > 0) {
                        endLine++;
                        const nextLine = document.lineAt(endLine).text;
                        if (nextLine.includes('{')) bracketCount++;
                        if (nextLine.includes('}')) bracketCount--;
                    }
                    
                    const range = new vscode.Range(
                        new vscode.Position(i, 0),
                        new vscode.Position(endLine, document.lineAt(endLine).text.length)
                    );
                    
                    const selectionRange = new vscode.Range(
                        new vscode.Position(i, 0),
                        new vscode.Position(i, line.text.length)
                    );

                    const detail = className ? `${type} in ${className}` : type;
                    const kind = type === 'method' ? 
                        vscode.SymbolKind.Method : 
                        vscode.SymbolKind.Function;
                    console.log('methodMatch', methodMatch, range, selectionRange);
                    symbols.push(new vscode.DocumentSymbol(
                        name,
                        detail,
                        kind,
                        range,
                        selectionRange
                    ));
                }
            }
            
            resolve(symbols);
        });
    }
}

class GPDefinitionProvider implements vscode.DefinitionProvider {
    private definitionCache = new Map<string, vscode.Location[]>();
    private fileCache = new Map<string, number>(); // file path -> last modified timestamp
    private isIndexing = false;
    private readonly SUPPORTED_EXTENSIONS = ['.gp', '.ubl', '.ubp'];

    constructor() {
        // Initial indexing
        this.updateCache().catch(err => {
            console.error('Initial indexing failed:', err);
        });

        // Watch for file changes
        vscode.workspace.onDidChangeWorkspaceFolders(() => {
            this.updateCache().catch(err => {
                console.error('Workspace change indexing failed:', err);
            });
        });

        // Watch for file saves
        vscode.workspace.onDidSaveTextDocument((document) => {
            if (this.isSupportedFile(document.uri)) {
                this.updateSingleFile(document.uri).catch(err => {
                    console.error('File save indexing failed:', err);
                });
            }
        });
    }

    private isSupportedFile(uri: vscode.Uri): boolean {
        const ext = uri.fsPath.toLowerCase().slice(uri.fsPath.lastIndexOf('.'));
        return this.SUPPORTED_EXTENSIONS.includes(ext);
    }

    private async updateCache(): Promise<void> {
        if (this.isIndexing) {
            return;
        }

        try {
            this.isIndexing = true;
            
            // Find all supported files
            const allFiles = await Promise.all(
                this.SUPPORTED_EXTENSIONS.map(ext => 
                    vscode.workspace.findFiles(`**/*${ext}`)
                )
            );
            const files = allFiles.flat();

            // Check which files need updating
            const filesToUpdate: vscode.Uri[] = [];
            for (const file of files) {
                try {
                    const stat = await vscode.workspace.fs.stat(file);
                    const lastModified = this.fileCache.get(file.fsPath);
                    
                    if (!lastModified || lastModified < stat.mtime) {
                        filesToUpdate.push(file);
                        this.fileCache.set(file.fsPath, stat.mtime);
                    }
                } catch (err) {
                    console.warn(`Failed to check file ${file.fsPath}:`, err);
                    filesToUpdate.push(file);
                }
            }

            // Remove entries for deleted files
            const currentFiles = new Set(files.map(f => f.fsPath));
            for (const [filePath] of this.fileCache) {
                if (!currentFiles.has(filePath)) {
                    this.fileCache.delete(filePath);
                    // Remove definitions from deleted files
                    for (const [funcName, locations] of this.definitionCache.entries()) {
                        const filteredLocations = locations.filter(loc => loc.uri.fsPath !== filePath);
                        if (filteredLocations.length === 0) {
                            this.definitionCache.delete(funcName);
                        } else {
                            this.definitionCache.set(funcName, filteredLocations);
                        }
                    }
                }
            }

            // Only index files that have changed
            if (filesToUpdate.length > 0) {
                await Promise.all(filesToUpdate.map(async (file) => {
                    try {
                        await this.indexFile(file);
                    } catch (err) {
                        console.warn(`Failed to index file ${file.fsPath}:`, err);
                    }
                }));

                const fileTypes = filesToUpdate
                    .map(f => f.fsPath.slice(f.fsPath.lastIndexOf('.')))
                    .reduce((acc, ext) => {
                        acc[ext] = (acc[ext] || 0) + 1;
                        return acc;
                    }, {} as Record<string, number>);

                const fileTypeSummary = Object.entries(fileTypes)
                    .map(([ext, count]) => `${count} ${ext}`)
                    .join(', ');

                vscode.window.setStatusBarMessage(
                    `GP: Indexed ${filesToUpdate.length} files (${fileTypeSummary})`, 
                    3000
                );
            }

        } catch (err) {
            console.error('Indexing failed:', err);
            vscode.window.showErrorMessage('GP: Indexing failed, some features may be limited');
        } finally {
            this.isIndexing = false;
        }
    }

    private async updateSingleFile(uri: vscode.Uri): Promise<void> {
        try {
            // Remove old definitions for this file
            const filePath = uri.fsPath;
            for (const [funcName, locations] of this.definitionCache.entries()) {
                const filteredLocations = locations.filter(loc => loc.uri.fsPath !== filePath);
                if (filteredLocations.length === 0) {
                    this.definitionCache.delete(funcName);
                } else {
                    this.definitionCache.set(funcName, filteredLocations);
                }
            }

            // Index the updated file
            await this.indexFile(uri);
            
            // Update file cache
            const stat = await vscode.workspace.fs.stat(uri);
            this.fileCache.set(filePath, stat.mtime);

        } catch (err) {
            console.warn(`Failed to update index for file ${uri.fsPath}:`, err);
            throw err;
        }
    }

    private async indexFile(file: vscode.Uri): Promise<void> {
        try {
            const document = await vscode.workspace.openTextDocument(file);
            
            for (let i = 0; i < document.lineCount; i++) {
                const line = document.lineAt(i);
                const text = line.text.trim();
                
                // Match method or to definitions
                const methodMatch = text.match(/^(method|to)\s+(\w+)(?:\s+(\w+))?/);
                if (methodMatch) {
                    const funcName = methodMatch[2];
                    const location = new vscode.Location(
                        document.uri,
                        new vscode.Position(i, 0)
                    );
                    
                    const existing = this.definitionCache.get(funcName) || [];
                    existing.push(location);
                    this.definitionCache.set(funcName, existing);
                }
            }
        } catch (err) {
            console.warn(`Error indexing file ${file.fsPath}:`, err);
            throw err;
        }
    }

    public async provideDefinition(
        document: vscode.TextDocument,
        position: vscode.Position,
        token: vscode.CancellationToken
    ): Promise<vscode.Definition | undefined> {
        try {
            const wordRange = document.getWordRangeAtPosition(position);
            if (!wordRange) {
                return undefined;
            }

            const word = document.getText(wordRange);

            // Check cache first
            const cachedLocations = this.definitionCache.get(word);
            if (cachedLocations) {
                return cachedLocations;
            }

            // Fallback to searching in current file if not in cache
            return await this.findDefinitionInFile(document, word);
        } catch (err) {
            console.error('Error providing definition:', err);
            return undefined;
        }
    }

    private async findDefinitionInFile(
        document: vscode.TextDocument,
        word: string
    ): Promise<vscode.Location | undefined> {
        try {
            for (let i = 0; i < document.lineCount; i++) {
                const line = document.lineAt(i);
                const text = line.text.trim();
                
                // Match method or to definitions
                const methodMatch = text.match(/^(method|to)\s+(\w+)(?:\s+(\w+))?/);
                if (methodMatch && methodMatch[2] === word) {
                    return new vscode.Location(
                        document.uri,
                        new vscode.Position(i, 0)
                    );
                }
            }
        } catch (err) {
            console.warn('Error searching in file:', err);
        }
        return undefined;
    }

    public getIndexedFunctions(): Set<string> {
        return new Set(this.definitionCache.keys());
    }

    public async updateSemanticTokens(document: vscode.TextDocument): Promise<vscode.SemanticTokens> {
        const tokensBuilder = new vscode.SemanticTokensBuilder();
        const indexedFunctions = this.getIndexedFunctions();

        for (let i = 0; i < document.lineCount; i++) {
            const line = document.lineAt(i);
            const text = line.text;

            // Match function calls with class and parameters
            const functionCallRegex = /(\w+)\s+(\w+)\s*\((.*?)\)/g;
            let match;

            while ((match = functionCallRegex.exec(text)) !== null) {
                const [fullMatch, funcName, className, params] = match;
                if (indexedFunctions.has(funcName)) {
                    // Add semantic token for function name
                    tokensBuilder.push(
                        i,                  // line
                        match.index,        // character
                        funcName.length,    // length
                        0,                  // tokenType: function
                        0                   // tokenModifiers: none
                    );

                    // Add semantic token for class name
                    tokensBuilder.push(
                        i,                                      // line
                        match.index + funcName.length + 1,      // character
                        className.length,                       // length
                        1,                                      // tokenType: class
                        0                                       // tokenModifiers: none
                    );

                    // Add semantic tokens for parameters
                    const paramStart = match.index + fullMatch.length - params.length - 1;
                    params.split(',').forEach((param, index) => {
                        const trimmedParam = param.trim();
                        const paramOffset = params.indexOf(trimmedParam, index === 0 ? 0 : params.indexOf(',', index - 1) + 1);
                        tokensBuilder.push(
                            i,                              // line
                            paramStart + paramOffset,       // character
                            trimmedParam.length,           // length
                            2,                              // tokenType: parameter
                            0                               // tokenModifiers: none
                        );
                    });
                }
            }
        }

        return tokensBuilder.build();
    }
}

export function activate(context: vscode.ExtensionContext) {
    console.log('GP Lang init');

    // Register the definition provider for all supported file types
    const gpDefinitionProvider = new GPDefinitionProvider();
    
    context.subscriptions.push(
        vscode.languages.registerDefinitionProvider(
            [
                { scheme: 'file', language: 'gp' },
                { scheme: 'file', language: 'ubl' },
                { scheme: 'file', language: 'ubp' }
            ],
            gpDefinitionProvider
        )
    );

    context.subscriptions.push(
        vscode.languages.registerDocumentSymbolProvider('gp', new GPDocumentSymbolProvider())
    );

    context.subscriptions.push(
        vscode.languages.registerDocumentFormattingEditProvider('gp', {
            provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
                const edits: vscode.TextEdit[] = [];
                // TODO: 
                return edits;
            }
        })
    );

    context.subscriptions.push(
        vscode.languages.registerHoverProvider('gp', {
            provideHover(document: vscode.TextDocument, position: vscode.Position) {
                const wordRange = document.getWordRangeAtPosition(position);
                const word = document.getText(wordRange);
                
                // TODO: 
                return new vscode.Hover(`Hover info for: ${word}`);
            }
        })
    );

    context.subscriptions.push(
        vscode.languages.registerCompletionItemProvider('gp', {
            provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
                // TODO: auto complete
                const completionItems: vscode.CompletionItem[] = [];
                
                // TODO: more keywords
                const keywords = ['if', 'else', 'return', 'method', 'to', 'class', 'for', 'while', 'repeat'];
                keywords.forEach(keyword => {
                    completionItems.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword));
                });

                return completionItems;
            }
        })
    );

    // Register semantic token provider
    context.subscriptions.push(
        vscode.languages.registerDocumentSemanticTokensProvider(
            [
                { scheme: 'file', language: 'gp' },
                { scheme: 'file', language: 'ubl' },
                { scheme: 'file', language: 'ubp' }
            ],
            {
                provideDocumentSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult<vscode.SemanticTokens> {
                    return gpDefinitionProvider.updateSemanticTokens(document);
                }
            },
            {
                tokenTypes: ['function', 'class', 'parameter'],
                tokenModifiers: []
            }
        )
    );
}

export function deactivate() {}